/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.google.code.peersim.pastry.protocol;

import java.util.UUID;

/**
 * This message represents a join request issued by a node with the purpose of
 * joining a pastry network. In such a message the key stores the {@link PastryId}
 * of the joining node.
 *
 * @author frusso
 * @version 0.1
 * @since 0.1
 */
class JoinRequest extends PastryMessage {

  /**
   * Constructor.
   *
   * @param key The id of the joining node
   * @param src The sender
   * @param dst The destination
   */
  JoinRequest(PastryId key, PastryNode src, PastryNode dst) {
    super();
    super.setType(Type.JOIN_REQ);
    super.setKey(key);
    super.setOrignator(src);
    super.setSender(src);
    super.setDestination(dst);
  }

  /**
   * Forwards the current message to the provided next destination. Note that the
   * newly created message inherits the number of hops of the current message.
   *
   * @param nextDst The next step
   * @return The new {@link JoinRequest} instance
   */
  JoinRequest forward(PastryNode nextDst) {
    JoinRequest f = new JoinRequest(this.getKey(), this.getSrc(), nextDst);
    f.setOrignator(this.getOriginalSender());
    f.setHops(this.getHops());
    return f;
  }

  /**
   * Answers the current message with a {@link JoinOk} message stating that the
   * node that issued the {@link JoinRequest} has been accepted as part of the
   * network. As prescribed by the protocol specification, state-tables are also
   * exchanged at this step.<br>
   * Note that the newly created message inherits the number of hops of the current message.
   *
   * @param routingTable The routing table of the accepting node
   * @param leafSet The leaf-set of the accepting node
   * @param neighborhoodSet The neighborhood-set of the accepting node
   * @return The {@link JoinOk} message
   */
  JoinOk replyWithJoinOk(PastryRoutingTable routingTable, PastryLeafSet leafSet, PastryNeighborhoodSet neighborhoodSet) {
    JoinOk msg = new JoinOk(getKey(), getDst(), getOriginalSender(), routingTable, leafSet, neighborhoodSet, getId());
    msg.setOrignator(getOriginalSender());
    msg.setHops(this.getHops());
    return msg;
  }

  /**
   * Answers the current message with a {@link StateTables} message, meaning that
   * the join request has not been accepted yet, but has to be forwarded to another
   * node, the {@code nextHop}. As prescribed by the protocol, state-tables are
   * exchanged at this step.<br>
   * Note that the newly created message inherits the number of hops of the current message.
   *
   * @param routingTable The routing table of the accepting node
   * @param leafSet The leaf-set of the accepting node
   * @param neighborhoodSet The neighborhood-set of the accepting node
   * @param nextHop The next node to forward the message to
   * @return The {@link StateTables} message
   */
  StateTables replyWithStateTables(PastryRoutingTable routingTable, PastryLeafSet leafSet, PastryNeighborhoodSet neighborhoodSet, PastryId nextHop) {
    StateTables msg = new StateTables(nextHop, getDst(), getOriginalSender(), routingTable, leafSet, neighborhoodSet, getId());
    msg.setHops(this.getHops());
    return msg;
  }

  /**
   * Message used to ack that a {@link JoinRequest} has been received but not yet
   * accepted. Stores the sending node state-tables along with a next-hop indication
   * to the joining node. This message reconciliation-id serves the purpose of helping
   * the joining node matching this reply with the pending request. The reconciliation-id
   * matches the message-id of the join request.
   *
   * @author frusso
   * @version 0.1
   * @since 0.1
   */
  static class StateTables extends JoinRequest {

    private final UUID reconciliationId;
    private final PastryRoutingTable routingTable;
    private final PastryLeafSet leafSet;
    private final PastryNeighborhoodSet neighborhoodSet;

    /**
     * Constructor. This must be used when the joining node receives a {@link JoinOk}
     * message and has to reply with its own state-tables. In such a case no
     * reconciliation-id is required.
     *
     * @param nextHop The next node that should be contacted
     * @param src The sender
     * @param dst The destination
     * @param routingTable The routing table of the accepting node
     * @param leafSet The leaf-set of the accepting node
     * @param neighborhoodSet The neighborhood-set of the accepting node
     */
    StateTables (PastryId nextHop, PastryNode src, PastryNode dst, PastryRoutingTable routingTable,
      PastryLeafSet leafSet, PastryNeighborhoodSet neighborhoodSet) {
      this(nextHop, src, dst, routingTable, leafSet, neighborhoodSet, null);
    }

    /**
     * Constructor. This must be used when replying to a {@link JoinRequest}. The
     * {@link StateTables} message must be related to the {@link JoinRequest} by
     * means of the correlation-id.
     *
     * @param nextHop The next node that should be contacted
     * @param src The sender
     * @param dst The destination
     * @param routingTable The routing table of the accepting node
     * @param leafSet The leaf-set of the accepting node
     * @param neighborhoodSet The neighborhood-set of the accepting node
     * @param recId The message-id of the {@link JoinRequest} to reply to
     */
    private StateTables(PastryId nextHop, PastryNode src, PastryNode dst, PastryRoutingTable routingTable,
      PastryLeafSet leafSet, PastryNeighborhoodSet neighborhoodSet, UUID recId) {
      super(nextHop, src, dst);
      super.setType(Type.STATE_TABLES);
      super.setOrignator(src);
      super.setSender(src);
      super.setDestination(dst);
      this.routingTable = routingTable;
      this.leafSet = leafSet;
      this.neighborhoodSet = neighborhoodSet;
      this.reconciliationId = recId;
    }

    /**
     * Returns the leaf-set.
     * @return The leafSet
     */
    public PastryLeafSet getLeafSet() {
      return leafSet;
    }

    /**
     * Returns the neighborhood-set
     * @return The neighborhoodSet
     */
    public PastryNeighborhoodSet getNeighborhoodSet() {
      return neighborhoodSet;
    }

    /**
     * Returns the next-hop indication
     * @return The next-hop
     */
    public PastryId getNextHop() {
      return getKey();
    }

    /**
     * Returns the reconciliation-id
     * @return The reconciliation-id
     */
    public UUID getReconciliationId() {
      return reconciliationId;
    }

    /**
     * Returns the routing-table
     * @return The routingTable
     */
    public PastryRoutingTable getRoutingTable() {
      return routingTable;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
      return getType().toString()+", hops "+getHops()+" next hop "+getKey()+" from "+getSrc().getPastryId()+" to "+getDst().getPastryId();
    }
  }

  /**
   * Message used to ack that a {@link JoinRequest} has been received and
   * accepted. Stores the sending node state-tables. This message reconciliation-id
   * serves the purpose of helping the joining node matching this reply with the
   * pending request. The reconciliation-id matches the message-id of the join request.
   * Note: in this case the message key is the {@link PastryId} of the joining node
   * whose join request has been accepted.
   *
   * @author frusso
   * @version 0.1
   * @since 0.1
   */
  static class JoinOk extends StateTables {

    /**
     * Constructor.
     *
     * @param joiningNodeId The id of the newly joined node
     * @param src The sender
     * @param dst The destination
     * @param routingTable The routing table of the accepting node
     * @param leafSet The leaf-set of the accepting node
     * @param neighborhoodSet The neighborhood-set of the accepting node
     * @param recId The message-id of the {@link JoinRequest} to reply to
     */
    private JoinOk(PastryId joiningNodeId, PastryNode src, PastryNode dst, PastryRoutingTable routingTable,
      PastryLeafSet leafSet, PastryNeighborhoodSet neighborhoodSet, UUID recId) {
      super(joiningNodeId, src, dst, routingTable, leafSet, neighborhoodSet, recId);
      this.setType(Type.JOIN_OK);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
      return getType().toString()+", hops "+getHops()+" key "+getKey()+" from "+getSrc().getPastryId()+" to "+getDst().getPastryId();
    }
  }
}